﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Shop.Core.Properties;

namespace Shop.Core.Domain.Common
{
    [Serializable]
    public class Money
    {
        /// <summary>
        /// 钱！
        /// </summary>
        /// <param name="value">金额</param>
        /// <param name="rounding">四舍五入类型</param>
        /// <param name="precision">精度</param>
        /// <param name="isAutoRounding">是否自动四舍五入</param>
        public Money(decimal value, bool isAutoRounding = true, MidpointRounding rounding = MidpointRounding.ToEven,
            int precision = 2)
        {
            this.value = value;
            this.rounding = rounding;
            this.precision = precision;
            this.isAutoRounding = isAutoRounding;
            this.Round();
        }

        private decimal value;
        private MidpointRounding rounding;
        private int precision;
        private bool isAutoRounding;
        /// <summary>
        /// 金额
        /// </summary>
        public decimal Value
        {
            get
            {
                return this.value;
            }
            private set
            {
                this.value = value;
            }
        }

        /// <summary>
        /// 是否具有相同的四舍五入方式
        /// </summary>
        /// <param name="moneyToCompare">需要比较的Money</param>
        /// <returns>true/false</returns>
        public bool HasSameRounding(Money moneyToCompare)
        {
            return this.rounding == moneyToCompare.rounding;
        }
        /// <summary>
        /// 是否具有相同的自动四舍五入设置
        /// </summary>
        /// <param name="moneyToCompare"></param>
        /// <returns></returns>
        public bool HasSameAutoRounding(Money moneyToCompare)
        {
            return this.isAutoRounding == moneyToCompare.isAutoRounding;
        }

        /// <summary>
        /// 采用“四舍六入五成双”圆整指定金额为两位小数。
        /// </summary>
        /// <remarks>
        /// 使用指定的舍入原则对当前对象进行舍入并返回新对象。
        /// </remarks>
        /// <param name="precision">精度</param>
        /// <param name="rounding">四舍五入原则</param>
        /// <returns>使用指定参数生成的新对象</returns>
        public Money RoundTo(int precision, MidpointRounding rounding)
        {
            return new Money(decimal.Round(value, precision, rounding),
                isAutoRounding, this.rounding, this.precision);
        }
        /// <summary>
        /// 内部使用。按照设定的参数对Money进行舍入。
        /// </summary>
        private void Round()
        {
            if (this.isAutoRounding)
            {
                this.value = decimal.Round(this.value, this.precision, this.rounding);
            }
        }

        /// <summary>
        /// 获取Round方式
        /// </summary>
        public MidpointRounding Rounding
        {
            get
            {
                return this.rounding;
            }
            private set
            {
                this.rounding = value;
            }
        }
        /// <summary>
        /// 获取精度设置
        /// </summary>
        public int Precision
        {
            get
            {
                return this.precision;
            }
            private set
            {
                this.precision = value;
            }
        }
        /// <summary>
        /// 获取是否自动Round
        /// </summary>
        public bool IsAutoRounding
        {
            get
            {
                return this.isAutoRounding;
            }
            private set
            {
                this.isAutoRounding = value;
            }
        }

        /// <summary>
        /// 获取HashCode（已重载）
        /// </summary>
        /// <returns>Hash Code</returns>
        public override int GetHashCode()
        {
            return value.GetHashCode() ^ isAutoRounding.GetHashCode() ^ precision.GetHashCode()
                ^ rounding.GetHashCode();
        }

        /// <summary>
        /// 判断两个对象是否具有相同的值。注意是值比较，不是引用比较。
        /// </summary>
        /// <param name="obj">用于比较的对象</param>
        /// <returns>true/false</returns>
        public override bool Equals(object obj)
        {
            if (obj != null && obj is Money)
            {
                return (Money)obj == this;
            }
            return false;
        }

        /// <summary>
        /// 判断当前Money实例与指定的<paramref name="obj"/>是否相等。
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public bool Equals(Money obj)
        {
            return obj == this;
        }

        /// <summary>
        /// 已重载。生成字符串。格式为货币缩写+货币符号+货币值。如“USD$ 1.00”。
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return value.ToString();
        }
        /// <summary>
        /// 格式化输出货币字符串
        /// </summary>
        /// <param name="format">格式</param>
        /// <returns>用于显示的货币字符串</returns>
        public string ToString(IFormatProvider format)
        {
            throw new NotImplementedException();
        }
        public string ToString(string format)
        {
            throw new NotImplementedException();
        }
        public string ToString(string format, IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        #region op

        public static Money operator +(Money money, decimal value)
        {
            decimal amount = money.value + value;
            return new Money(amount, money.isAutoRounding,
                money.rounding, money.precision);
        }

        public static Money operator +(Money money1, Money money2)
        {
            //CheckParamHasSameCurrency(money1, money2);
            CheckParamHasSameRounding(money1, money2);
            CheckParamHasSameAutoRounding(money1, money2);
            decimal amount = money1.value + money2.value;
            return new Money(amount, money1.isAutoRounding,
                money1.rounding, Math.Max(money1.precision, money2.precision));
        }

        public static Money operator -(Money money, decimal value)
        {
            decimal sumVal = money.value - value;
            return new Money(sumVal, money.isAutoRounding,
                money.rounding, money.precision);
        }

        public static Money operator -(Money money1, Money money2)
        {
            //CheckParamHasSameCurrency(money1, money2);
            CheckParamHasSameRounding(money1, money2);
            CheckParamHasSameAutoRounding(money1, money2);
            decimal sumVal = money1.value - money2.value;
            return new Money(sumVal, money1.isAutoRounding,
                money1.rounding, Math.Max(money1.precision, money2.precision));
        }

        public static Money operator *(Money money, decimal times)
        {
            decimal sumVal = money.value * times;
            var result = new Money(sumVal, money.isAutoRounding,
                money.rounding, money.precision);
            result.Round();
            return result;
        }

        public static Money operator /(Money money, decimal divider)
        {
            decimal resultVal = money.value / divider;
            var result = new Money(resultVal, money.isAutoRounding,
                money.rounding, money.precision);
            result.Round();
            return result;
        }

        public static bool operator >(Money money1, Money money2)
        {
            //CheckParamHasSameCurrency(money1, money2);
            return money1.value > money2.value;
        }

        public static bool operator >=(Money money1, Money money2)
        {
            return !(money1 < money2);
        }

        public static bool operator <(Money money1, Money money2)
        {
            //CheckParamHasSameCurrency(money1, money2);
            return money1.value < money2.value;
        }

        public static bool operator <=(Money money1, Money money2)
        {
            return !(money1 > money2);
        }

        public static bool operator ==(Money money1, Money money2)
        {
            return money1.value == money2.value;
        }

        public static bool operator !=(Money money1, Money money2)
        {
            return money1.value != money2.value;
        }

        #endregion

        #region private helper

        /// <summary>
        /// 检查两个Money是否具有相同的四舍五入方式。若不是则抛出异常。
        /// </summary>
        /// <param name="money1">第一个Money</param>
        /// <param name="money2">第二个Money</param>
        static void CheckParamHasSameRounding(Money money1, Money money2)
        {
            if (!money1.HasSameRounding(money2))
            {
                throw new ArgumentException(Resources.Money_RoundingMustIdentical);
            }
        }
        /// <summary>
        /// 检查两个Money是否具有相同的自动舍入设置。若不是则抛出异常。
        /// </summary>
        /// <param name="money1">第一个Money</param>
        /// <param name="money2">还是Money</param>
        static void CheckParamHasSameAutoRounding(Money money1, Money money2)
        {
            if (!money1.HasSameAutoRounding(money2))
            {
                throw new ArgumentException(Resources.Money_AutoRoundingMustIdentical);
            }
        }

        #endregion

        public static Money Zero
        {
            get
            {
                return new Money(0);
            }
        }
    }
}
